package camid.services.nodemanager;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.activeobject.ProxyBuilder;
import camid.distribution.broker.Requestor;
import camid.distribution.interceptor.InterceptorChain;
import camid.distribution.interceptor.InvocationInterceptor;
import camid.distribution.lifecycle.LifecycleScope;
import camid.distribution.protocol.Endpoint;
import camid.distribution.protocol.InvocationDescriptor;
import camid.distribution.protocol.MethodRequestMessage;
import camid.distribution.protocol.MethodResponseMessage;
import camid.services.nameservice.EntityMetadata;
import camid.services.nameservice.INameService;
import camid.services.nameservice.ObjectReference;
import camid.util.monitoring.JMeterClientLogger;

public class LookupInterceptor implements InvocationInterceptor {

	public static final String FUTURE_REQUEST = "LookupFutureRequest";
	
	public static final String LAST_RELOOKUP = "LastRelookup";

	private static final Long RELOOKUP_TIME = 60000L;

	private static final Logger logger = LogManager.getLogger(Requestor.class.getName());

	private INameService ns;

	private Requestor requestor;

	private Random random;
	
	private ExecutorService executor;
	
	private NextRelookup nextRelookup;

	public LookupInterceptor(INameService ns, Requestor requestor) {
		this.ns = ns;
		this.requestor = requestor;
		this.random = new Random();
		executor = Executors.newFixedThreadPool(10);
		nextRelookup = new NextRelookup();
	}

	@Override
	public void beforeInvocation(MethodRequestMessage requestMessage)
			throws Exception {
//		final Long startTime = System.currentTimeMillis();
		final String objectId = requestMessage.getDescriptor().getObjectId();
		final String entityClass = requestMessage.getDescriptor().getEntityClass();
//		final Endpoint endpoint = requestMessage.getEndpoint();
		final InvocationDescriptor descriptor = requestMessage.getDescriptor();
		final EntityMetadata metadata = descriptor.getMetadata();

		if(metadata.getLifeCycleScope().equals(LifecycleScope.STATIC))
		{
			return;
		}

		Map<String, Object> invocationContext = requestMessage.getInvocationContext();

		Callable<ObjectReference> task = null;
		
		if(!invocationContext.containsKey(LAST_RELOOKUP))
		{
			long salt = random.nextLong() * ( RELOOKUP_TIME / 2L );
			invocationContext.put(LAST_RELOOKUP, (Long)System.currentTimeMillis() + salt);
		}
		else
		{
			Long lastRelookupTimestamp = (Long) invocationContext.get(LAST_RELOOKUP);
			if(timeElapsed(lastRelookupTimestamp))
			{
				long salt = random.nextLong() * ( RELOOKUP_TIME / 2L ); 
				invocationContext.put(LAST_RELOOKUP, (Long)System.currentTimeMillis() + salt);
//				task = new Callable<ObjectReference>() {
//
//					@Override
//					public ObjectReference call() throws Exception {
//						ObjectReference reference = ns.lookupObjectIdLB(entityClass, objectId);
//						
//						return reference;
//					}
//					
//				};
			}
		}
		
		
//		if( task != null && executor != null )
//		{
//			Future<ObjectReference> future = executor.submit(task);
//			invocationContext.put(FUTURE_REQUEST, future);
//		}
		

	}

	

	@Override
	public void afterInvocation(MethodRequestMessage request, MethodResponseMessage responseMessage)
			throws Exception 
	{
		Long startTime = System.currentTimeMillis();
		final String objectId = request.getDescriptor().getObjectId();
		final String entityClass = request.getDescriptor().getEntityClass();
		final Endpoint endpoint = request.getEndpoint();
		final InvocationDescriptor descriptor = request.getDescriptor();
		final EntityMetadata metadata = descriptor.getMetadata();
		final Map<String,Object> invocationContext = request.getInvocationContext();
//		Future<ObjectReference> future = this.objRefMap.get(objectId);
		Future<ObjectReference> future = (Future<ObjectReference>) invocationContext.get(FUTURE_REQUEST);
		
		if(metadata.getLifeCycleScope().equals(LifecycleScope.STATIC))
		{
			return;
		}
		
		if(future != null)
		{
			ObjectReference objRef = future.get();
			if(objRef != null)
			{
				Endpoint newEndpoint = objRef.getEndpoint();
				if(!newEndpoint.equals(endpoint))
				{
					
					endpoint.setHostname(newEndpoint.getHostname());
					endpoint.setPort(newEndpoint.getPort());
					
					if(metadata.getLifeCycleScope().equals(LifecycleScope.PER_CLIENT) )
					{
						this.requestStateTransfer(descriptor, newEndpoint);
					}
					
				}
			}
		}
		
		Long endTime = System.currentTimeMillis();
		JMeterClientLogger jmLogger = JMeterClientLogger.getInstance();
		logger.info("Downtime (" + entityClass + "/" + objectId + "/"
				+ descriptor.getMethod() + ") => " + (endTime - startTime));
		jmLogger.info("Downtime (" + entityClass + "/" + objectId + "/"
				+ descriptor.getMethod() + ") => " + (endTime - startTime));
		
	}

	@Override
	public MethodResponseMessage intercept(InterceptorChain interceptorChain)
			throws Exception {
		final MethodRequestMessage requestMessage = interceptorChain.getRequestMessage();
		
		final String objectId = requestMessage.getDescriptor().getObjectId();
		final String entityClass = requestMessage.getDescriptor().getEntityClass();
		final Endpoint endpoint = requestMessage.getEndpoint();
		final InvocationDescriptor descriptor = requestMessage.getDescriptor();
		final EntityMetadata metadata = descriptor.getMetadata();
		final Map<String,Object> invocationContext = requestMessage.getInvocationContext();
		
		boolean doLookup = false;
		
		if(metadata.getLifeCycleScope().equals(LifecycleScope.STATIC))
		{
			return interceptorChain.proceed();
		}

		Callable<ObjectReference> task = null;
		
		if(!invocationContext.containsKey(LAST_RELOOKUP))
		{
			setNextRelookup(invocationContext);
		}
		else
		{
			boolean containsLoadAlert = invocationContext.containsKey(LoadAlertInterceptor.LOAD_ALERT);
			Boolean loadAlert = (Boolean) invocationContext.get(LoadAlertInterceptor.LOAD_ALERT);
			
			Long lastRelookupTimestamp = (Long) invocationContext.get(LAST_RELOOKUP);
			if( (!containsLoadAlert && timeElapsed(lastRelookupTimestamp)) || (loadAlert != null && loadAlert))
//			if(timeElapsed(lastRelookupTimestamp))
			{
				if(!containsLoadAlert) {
					setNextRelookup(invocationContext);
				}
				
				doLookup = true;
//				if(timeElapsed(lastRelookupTimestamp))
//				if(!containsLoadAlert || (loadAlert != null && loadAlert))
//				{
					
//					task = new Callable<ObjectReference>() {
//	
//						@Override
//						public ObjectReference call() throws Exception {
//							ObjectReference reference = ns.lookupObjectIdLB(entityClass, objectId);
//							
//							return reference;
//						}
//						
//					};
//				}
			}
		}
		
		MethodResponseMessage responseMessage = null;
		
		if( doLookup ) {
//		if( task != null && executor != null ) {
//			Future<ObjectReference> future = executor.submit(task);
			Future<ObjectReference> future = ns.lookupObjectIdLB(entityClass, objectId);
			responseMessage = doProceed(interceptorChain);
			
			Long startTime = System.currentTimeMillis();
			
			ObjectReference objRef = future.get();
			if(objRef != null)
			{
				Endpoint newEndpoint = objRef.getEndpoint();
				if(!newEndpoint.equals(endpoint))
				{
					endpoint.setHostname(newEndpoint.getHostname());
					endpoint.setPort(newEndpoint.getPort());
					
					if(metadata.getLifeCycleScope().equals(LifecycleScope.PER_CLIENT) )
					{
						this.requestStateTransfer(descriptor, newEndpoint);
					}
				}
			}
			
			Long endTime = System.currentTimeMillis();
//			JMeterClientLogger jmLogger = JMeterClientLogger.getInstance();
			logger.info("Downtime (" + entityClass + "/" + objectId + "/"
					+ descriptor.getMethod() + ") => " + (endTime - startTime));
//			jmLogger.info("Downtime (" + entityClass + "/" + objectId + "/"
//					+ descriptor.getMethod() + ") => " + (endTime - startTime));
			
		} else {
			responseMessage = doProceed(interceptorChain);
		}
		
		return responseMessage;
	}
	
	private void setNextRelookup(Map<String, Object> invocationContext) {
//		Double salt = random.nextDouble() * ( RELOOKUP_TIME.doubleValue() / 2.0 );
//		Long currentTime = System.currentTimeMillis();
//		Long nextLookupTime = currentTime + RELOOKUP_TIME + salt.longValue();
		Long nextLookupTime = nextRelookup.nextRelookup() + RELOOKUP_TIME;
		invocationContext.put(LAST_RELOOKUP, nextLookupTime);
	}
	
	private boolean timeElapsed(Long lastRelookupTimestamp) {
		Long currentTimestamp = System.currentTimeMillis();
		Long diff = currentTimestamp - lastRelookupTimestamp;
		if(diff > RELOOKUP_TIME)
		{
			return true;
		}
		return false;
	}
	
	private void requestStateTransfer(InvocationDescriptor descriptor, Endpoint newEndpoint)
	{
		ProxyBuilder<INodeManager> proxyBuilder = new ProxyBuilder<INodeManager>(
				INodeManager.class, descriptor.getSessionId(), NodeManager.NODE_MANAGER_OBJECTID, 
				NodeManager.class, requestor, newEndpoint);

		INodeManager nodeManager = proxyBuilder.newProxy();
		nodeManager.migrateObject(descriptor.getObjectId(), newEndpoint);
	}
	
	public MethodResponseMessage doProceed(InterceptorChain interceptorChain) throws Exception {
		
		MethodResponseMessage responseMessage = interceptorChain.proceed();
//		Map<String,Object> context = responseMessage.getInvocationContext();
//		
//		if(context.containsKey(LoadAlertInterceptor.LOAD_ALERT)) {
//			
//			Map<String,Object> reqContext = interceptorChain.getRequestMessage().getInvocationContext();
//			Object loadAlert = context.get(LoadAlertInterceptor.LOAD_ALERT);
//			
//			if (loadAlert != null) {
//				reqContext.put(LoadAlertInterceptor.LOAD_ALERT, loadAlert);
//			}
//		}
		return responseMessage;
	}

}
